package com.automannn.codeGenerator.core.parser.node.leaf;

import com.automannn.codeGenerator.core.context.InternalContextAdapter;
import com.automannn.codeGenerator.core.exception.MethodInvocationException;
import com.automannn.codeGenerator.core.exception.TemplateInitException;
import com.automannn.codeGenerator.core.exception.VelocityException;
import com.automannn.codeGenerator.core.introspection.VelMethod;
import com.automannn.codeGenerator.core.log.Log;
import com.automannn.codeGenerator.core.parser.Parser;
import com.automannn.codeGenerator.core.parser.node.SimpleNode;
import com.automannn.codeGenerator.core.runtime.RuntimeConstants;
import com.automannn.codeGenerator.core.util.ClassUtils;

/**
 * @author chenkh
 * @time 2021/3/2 12:39
 */
public class ASTIndex extends SimpleNode
{
    private final String methodName = "get";

    /**
     * Indicates if we are running in strict reference mode.
     */
    protected boolean strictRef = false;

    public ASTIndex(int i)
    {
        super(i);
    }

    public ASTIndex(Parser p, int i)
    {
        super(p, i);
    }

    public Object init(InternalContextAdapter context, Object data)
            throws TemplateInitException
    {
        super.init(context, data);
        strictRef = rsvc.getBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT, false);
        return data;
    }



    private final static Object[] noParams = {};
    private final static Class[] noTypes = {};
    /**
     * If argument is an Integer and negative, then return (o.size() - argument).
     * Otherwise return the original argument.  We use this to calculate the true
     * index of a negative index e.g., $foo[-1]. If no size() method is found on the
     * 'o' object, then we throw an VelocityException.
     * @param context Used to access the method cache.
     * @param node  ASTNode used for error reporting.
     */
    public static Object adjMinusIndexArg(Object argument, Object o,
                                          InternalContextAdapter context, SimpleNode node)
    {
        if (argument instanceof Integer && ((Integer)argument).intValue() < 0)
        {
            // The index value is a negative number, $foo[-1], so we want to actually
            // Index [size - value], so try and call the size method.
            VelMethod method = ClassUtils.getMethod("size", noParams, noTypes,
                    o, context, node, false);
            if (method == null)
            {
                // The object doesn't have a size method, so there is no notion of "at the end"
                throw new VelocityException(
                        "A 'size()' method required for negative value "
                                + ((Integer)argument).intValue() + " does not exist for class '"
                                + o.getClass().getName() + "' at " + Log.formatFileString(node));
            }

            Object size = null;
            try
            {
                size = method.invoke(o, noParams);
            }
            catch (Exception e)
            {
                throw new VelocityException("Error trying to calls the 'size()' method on '"
                        + o.getClass().getName() + "' at " + Log.formatFileString(node), e);
            }

            int sizeint = 0;
            try
            {
                sizeint = ((Integer)size).intValue();
            }
            catch (ClassCastException e)
            {
                // If size() doesn't return an Integer we want to report a pretty error
                throw new VelocityException("Method 'size()' on class '"
                        + o.getClass().getName() + "' returned '" + size.getClass().getName()
                        + "' when Integer was expected at " + Log.formatFileString(node));
            }

            argument = new Integer(sizeint + ((Integer)argument).intValue());
        }

        // Nothing to do, return the original argument
        return argument;
    }

    public Object execute(Object o, InternalContextAdapter context)
            throws MethodInvocationException
    {
        Object argument = jjtGetChild(0).value(context);
        // If negative, turn -1 into size - 1
        argument = adjMinusIndexArg(argument, o, context, this);
        Object [] params = {argument};
        Class[] paramClasses = {argument == null ? null : argument.getClass()};

        VelMethod method = ClassUtils.getMethod(methodName, params, paramClasses,
                o, context, this, strictRef);

        if (method == null) return null;

        try
        {
            /*
             *  get the returned object.  It may be null, and that is
             *  valid for something declared with a void return type.
             *  Since the caller is expecting something to be returned,
             *  as long as things are peachy, we can return an empty
             *  String so ASTReference() correctly figures out that
             *  all is well.
             */
            Object obj = method.invoke(o, params);

            if (obj == null)
            {
                if( method.getReturnType() == Void.TYPE)
                {
                    return "";
                }
            }

            return obj;
        }
        /**
         * pass through application level runtime exceptions
         */
        catch( RuntimeException e )
        {
            throw e;
        }
        catch( Exception e )
        {
            String msg = "Error invoking method 'get("
                    + (argument == null ? "null" : argument.getClass().getName())
                    + ")' in " + o.getClass().getName()
                    + " at " + Log.formatFileString(this);
            log.error(msg, e);
            throw new VelocityException(msg, e);
        }
    }
}
